<?php

namespace api\components\dataCenter;

use api\modules\app\models\MetaArea;
use api\utils\StringUtil;
use app\components\dataCenter\constants\DataField;
use Exception;

class SearchFilterBuilder
{
    //等于
    const OPERATOR_EQ = '=';
    //不等于
    const OPERATOR_NEQ = '<>';
    //不等于
    const OPERATOR_NEQ2 = '!=';
    //大于
    const OPERATOR_GT = '>';
    //大于等于
    const OPERATOR_GTE = '>=';
    //小于
    const OPERATOR_LT = '<';
    //小于等于
    const OPERATOR_LTE = '<=';
    //in 数据中的值是匹配值中的任意一个
    const OPERATOR_IN = 'in';
    //in 数据中的值不是匹配值中的任意一个
    const OPERATOR_NIN = 'nin';
    //字段是否存在
    const OPERATOR_EX = 'ex';
    //模糊匹配
    const OPERATOR_LIKE = 'like';
    //目标值在两个值之间
    const OPERATOR_BETWEEN = 'between';
    //时间范围
    const OPERATOR_TIME_RANGE = 'time_range';
    //字符串匹配查询
    const OPERATOR_QUERY_STRING = 'query_string';
    //多字段匹配
    const OPERATOR_MULTI_MATCH = 'multi_match';

    const OPERATOR_MATCH_PHRASE_PREFIX = 'match_phrase_prefix';

    //经纬度key
    const LOCATION_KEY = 'location';
    //区key
    const AREA_DISTRICT_KEY = 'area_district';
    //街道key
    const AREA_TOWN_KEY = 'town';
    //地址key
    const ADDRESS_KEY = 'address';

    /**
     * @var array|string|null
     */
    private $where;

    protected function operatorRule(string $operator): ?array
    {
        $rule = [
            static::OPERATOR_EQ => [//等于
                'operator' => 'eq.',
                'data_type' => [
                    DataField::FIELD_TYPE_INT, DataField::FIELD_TYPE_AREA, DataField::FIELD_TYPE_BOOLEAN,
                    DataField::FIELD_TYPE_FLOAT, DataField::FIELD_TYPE_STRING, DataField::FIELD_TYPE_TIME
                ],
                'value_possibles' => ['string', 'double', 'float', 'integer', 'boolean']
            ],
            static::OPERATOR_NEQ => [//不等于
                'operator' => 'neq.',
                'data_type' => [
                    DataField::FIELD_TYPE_INT, DataField::FIELD_TYPE_AREA, DataField::FIELD_TYPE_BOOLEAN,
                    DataField::FIELD_TYPE_FLOAT, DataField::FIELD_TYPE_STRING
                ],
                'value_possibles' => ['string', 'double', 'float', 'integer', 'boolean']
            ],
            static::OPERATOR_NEQ2 => [//不等于
                'operator' => 'neq.',
                'data_type' => [
                    DataField::FIELD_TYPE_INT, DataField::FIELD_TYPE_AREA, DataField::FIELD_TYPE_BOOLEAN,
                    DataField::FIELD_TYPE_FLOAT, DataField::FIELD_TYPE_STRING
                ],
                'value_possibles' => ['string', 'double', 'float', 'integer', 'boolean']
            ],
            static::OPERATOR_GT => [//大于
                'operator' => 'gt.',
                'data_type' => [DataField::FIELD_TYPE_INT, DataField::FIELD_TYPE_FLOAT],
                'value_possibles' => ['string', 'double', 'float', 'integer']
            ],
            static::OPERATOR_GTE => [//大于等于
                'operator' => 'gte.',
                'data_type' => [DataField::FIELD_TYPE_INT, DataField::FIELD_TYPE_FLOAT],
                'value_possibles' => ['string', 'double', 'float', 'integer']
            ],
            static::OPERATOR_LT => [//小于
                'operator' => 'lt.',
                'data_type' => [DataField::FIELD_TYPE_INT, DataField::FIELD_TYPE_FLOAT],
                'value_possibles' => ['string', 'double', 'float', 'integer']
            ],
            static::OPERATOR_LTE => [//小于等于
                'operator' => 'lte.',
                'data_type' => [DataField::FIELD_TYPE_INT, DataField::FIELD_TYPE_FLOAT],
                'value_possibles' => ['string', 'double', 'float', 'integer']
            ],
            static::OPERATOR_IN => [//in 数据中的值是匹配值中的任意一个
                'operator' => 'in.',
                'data_type' => [
                    DataField::FIELD_TYPE_INT, DataField::FIELD_TYPE_FLOAT,
                    DataField::FIELD_TYPE_STRING, DataField::FIELD_TYPE_AREA
                ],
                'value_possibles' => ['array'],
                'array_item_possibles' => ['string', 'double', 'float', 'integer']
            ],
            static::OPERATOR_NIN => [//not in 数据中的值不是匹配值中的任意一个
                'operator' => 'nin.',
                'data_type' => [
                    DataField::FIELD_TYPE_INT, DataField::FIELD_TYPE_FLOAT,
                    DataField::FIELD_TYPE_STRING, DataField::FIELD_TYPE_AREA
                ],
                'value_possibles' => ['array'],
                'array_item_possibles' => ['string', 'double', 'float', 'integer']
            ],
            static::OPERATOR_EX => [//字段是否存在
                'operator' => 'ex.',
                'data_type' => 'all',
                'value_possibles' => ['boolean', 'string', 'integer']
            ],
            static::OPERATOR_LIKE => [//模糊匹配
                'operator' => 'like.',
                'data_type' => [DataField::FIELD_TYPE_STRING],
                'value_possibles' => ['string']
            ],
            static::OPERATOR_BETWEEN => [//目标值在两个值之间
                'operator' => 'bte.',
                'data_type' => [DataField::FIELD_TYPE_INT, DataField::FIELD_TYPE_FLOAT],
                'value_possibles' => ['array'],
                'array_item_possibles' => ['string', 'integer', 'double', 'float']
            ],
            static::OPERATOR_TIME_RANGE => [//时间范围
                'operator' => '',
                'data_type' => [DataField::FIELD_TYPE_TIME],
                'value_possibles' => ['array'],
                'array_item_possibles' => ['string', 'double', 'float', 'integer']
            ],
            static::OPERATOR_QUERY_STRING => [//字符串匹配查询
                'operator' => 'queryString',
                'data_type' => [DataField::FIELD_TYPE_STRING],
                'value_possibles' => ['string', 'integer'] // 前端 post int id
            ],
            static::OPERATOR_MULTI_MATCH => [//多字段匹配
                'operator' => 'multiMatch',
                'data_type' => [DataField::FIELD_TYPE_STRING],
                'value_possibles' => ['string']
            ],
            static::OPERATOR_MATCH_PHRASE_PREFIX => [//匹配头模糊查询
                'operator' => 'matchPhrasePrefix',
                'data_type' => [DataField::FIELD_TYPE_STRING],
                'value_possibles' => ['string']
            ],
        ];
        return $rule[$operator] ?? null;
    }

    protected function isEmpty($value)
    {
        return $value === '' || $value === [] || $value === null || is_string($value) && trim($value) === '';
    }

    public function andWhere(array $condition): self
    {
        if ($this->where === null) {
            $this->where = $condition;
        } elseif (is_array($this->where) && isset($this->where[0]) && strcasecmp($this->where[0], 'and') === 0) {
            $this->where[] = $condition;
        } else {
            $this->where = ['and', $this->where, $condition];
        }
        return $this;
    }

    public function andRawWhere(string $condition): self
    {
        if ($this->isEmpty($condition)) return $this;
        $this->where = $this->where === null ? $condition : ['and', $this->where, ['raw', $condition]];
        return $this;
    }

    public function orWhere(array $condition): self
    {
        if ($this->where === null) {
            $this->where = $condition;
        } else {
            $this->where = ['or', $this->where, $condition];
        }
        return $this;
    }

    public function andFilterWhere(array $condition): self
    {
        $condition = $this->filterCondition($condition);
        if ($condition !== []) {
            $this->andWhere($condition);
        }
        return $this;
    }

    public function orFilterWhere(array $condition)
    {
        $condition = $this->filterCondition($condition);
        if ($condition !== []) {
            $this->orWhere($condition);
        }
        return $this;
    }

    /**
     * @param $condition
     * @return array
     */
    protected function filterCondition($condition)
    {
        if (!is_array($condition)) return $condition;

        if (!isset($condition[0])) {
            foreach ($condition as $name => $value) {
                if ($this->isEmpty($value)) {
                    unset($condition[$name]);
                }
            }
            return $condition;
        }

        $operator = array_shift($condition);
        if (in_array($operator, ['AND', 'OR', 'and', 'or'])) {
            foreach ($condition as $i => $operand) {
                $subCondition = $this->filterCondition($operand);
                if ($this->isEmpty($subCondition)) {
                    unset($condition[$i]);
                } else {
                    $condition[$i] = $subCondition;
                }
            }
            if (empty($condition)) return [];
        } else {
            if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) return [];
        }

        array_unshift($condition, $operator);

        return $condition;
    }

    /**
     * @param string|array $condition
     * @return string
     * @throws Exception
     */
    protected function filterTextFromWhere($condition): string
    {
        if (empty($condition)) return '';
        if (is_string($condition)) return $condition;

        if (isset($condition[0])) {
            $operator = array_shift($condition);
            $where = [];
            if (in_array($operator, ['AND', 'OR', 'and', 'or'])) {
                $operator = strtoupper($operator);
                foreach ($condition as $item) {
                    $item_where = $this->filterTextFromWhere($item);
                    $item_where && $where[] = $item_where;
                }
                return '(' . implode($operator == 'AND' ? '&' : '|', $where) . ')';
            } else if ($operator === 'raw') {
                return $condition[0] ?? '';
            } else {
                return $this->conditionToFilterText(
                    $condition[0], $condition[1], $operator,
                    $condition[2] ?? DataField::FIELD_TYPE_STRING
                );
            }
        } else {
            $where = [];
            foreach ($condition as $key => $item) {
                $where[] = $this->conditionToFilterText($key, $item);
            }
            return count($where) == 1 ? $where[0] : '(' . implode('&', $where) . ')';
        }
    }

    /**
     * @param string|array $condition
     * @return array
     * @throws Exception
     */
    protected function filterObjFromWhere($condition): array
    {
        if (empty($condition)) return [];

        if (isset($condition[0])) {
            $operator = array_shift($condition);
            $where = [];
            if (in_array($operator, ['AND', 'OR', 'and', 'or'])) {
                $operator = strtoupper($operator);
                foreach ($condition as $item) {
                    $item_where = $this->filterObjFromWhere($item);
                    !empty($item_where) && $where[] = $item_where;
                }
                switch (count($where)) {
                    case 0:
                        return [];
                    case 1:
                        return $where[0];
                    default:
                    case 2:
                        return ['type' => $operator == 'OR' ? 'logicOr' : 'logicAnd', 'conditionElements' => $where];
                }
            } else if ($operator === 'raw') {
                return isset($condition[0]) && trim($condition[0])
                    ? ['type' => 'condition', 'value' => $condition[0]] : [];
            } else {
                return [
                    'type' => 'condition',
                    'value' => $this->conditionToFilterText(
                        $condition[0], $condition[1], $operator,
                        $condition[2] ?? DataField::FIELD_TYPE_STRING
                    )
                ];
            }
        } else {
            $where = [];
            foreach ($condition as $key => $item) {
                $where[] = [
                    'type' => 'condition',
                    'value' => $this->conditionToFilterText($key, $item)
                ];
            }
            return ['type' => 'logicAnd', 'conditionElements' => $where];
        }
    }

    /**
     * 条件转查询语句
     * @param string $field
     * @param $value
     * @param string $operator
     * @param string $data_type
     * @return string
     * @throws Exception
     */
    private function conditionToFilterText(
        string $field, $value, string $operator = self::OPERATOR_EQ,
        string $data_type = DataField::FIELD_TYPE_STRING
    ): string
    {
        //若为时间范围 则数据类型默认为时间
        $operator == self::OPERATOR_TIME_RANGE && $data_type = DataField::FIELD_TYPE_TIME;

        if (is_array($value)) {
            if (count($value) == 1) {
                //若值为数组 且只有1个元素 且为 in 或 nin
                //则将操作符转换为 = 或 <> 以加快查询速度
                $value = $value[0];
                $operator == self::OPERATOR_IN && $operator = self::OPERATOR_EQ;
                $operator == self::OPERATOR_NIN && $operator = self::OPERATOR_NEQ;
            } else {
                //若值为数组 且为等于或不等于
                //则将操作符转换为 in 或 nin
                $operator == self::OPERATOR_EQ && $operator = self::OPERATOR_IN;
                $operator == self::OPERATOR_NEQ && $operator = self::OPERATOR_NIN;
                $operator == self::OPERATOR_NEQ2 && $operator = self::OPERATOR_NIN;
            }
        }
        $operator_rule = $this->operatorRule($operator);
        if (!isset($operator_rule) || !isset($value)) {
            throw new Exception('字段不存在或操作符错误');
        }
        if (!in_array(gettype($value), $operator_rule['value_possibles'])) {
            throw new Exception('字段值传递错误');
        }
        //若为数组且规定了数组内元素类型 则逐一判断
        if (is_array($value) && isset($operator_rule['array_item_possibles'])) {
            foreach ($value as $item) {
                if (!in_array(gettype($item), $operator_rule['array_item_possibles'])) {
                    throw new Exception('字段值传递错误');
                }
            }
        }

        switch ($operator) {
            case static::OPERATOR_IN:
            case static::OPERATOR_NIN:
                // in or not in
                if (empty($value)) throw new Exception('无法筛选空值');
                if ($data_type == DataField::FIELD_TYPE_AREA || in_array($field, [self::AREA_TOWN_KEY, self::AREA_DISTRICT_KEY])) {
                    //若为地区字段
                    //若传值为数字 使用areaID筛选（去除先导0） 否则使用areaName筛选
                    if (is_numeric($value[0])) {
                        $value = array_map(
                            function ($v) {
                                return $v->getCode();
                            },
                            MetaArea::findAll(['id' => $value])
                        );
                        $field_name = $field . '.rawIdentity';
                    } else {
                        $field_name = $field . '.areaName';
                    }
                } else {
                    $field_name = $field;
                }
                //若只有1个值 则使用 eq 或 neq 可以提高查询效率
                return count($value) == 1
                    ? $field_name . '=' . ($operator == static::OPERATOR_IN ? 'eq.' : 'neq.') . strval($value[0])
                    : $field_name . '=' . $operator . '.~' . implode('~', $value);
                break;
            case static::OPERATOR_EQ:
            case static::OPERATOR_NEQ:
            case static::OPERATOR_NEQ2:
            case static::OPERATOR_GT:
            case static::OPERATOR_GTE:
            case static::OPERATOR_LT:
            case static::OPERATOR_LTE:
            case static::OPERATOR_LIKE:
                if ($data_type == DataField::FIELD_TYPE_TIME) {
                    //时间格式无需传递eq 且必须改为时间戳格式
                    $value = is_numeric($value) ? $value : strtotime($value);
                    if (!$value) throw new Exception('时间格式错误');
                    return $field . '=' . strval($value);
                }
                if (
                    $data_type == DataField::FIELD_TYPE_AREA ||
                    in_array($field, [self::AREA_TOWN_KEY, self::AREA_DISTRICT_KEY])
                ) {
                    //若为地区字段
                    //若传递值为数字 使用rawIdentity筛选 否则使用areaName筛选
                    return is_numeric($value)
                        ? $field . '.rawIdentity=' . $operator_rule['operator'] . MetaArea::findOne(['id' => intval($value)])->getCode()
                        : $field . '.areaName=' . $operator_rule['operator'] . $value;
                } else {
                    gettype($value) == 'boolean' && $value = $value ? 'true' : 'false';
                    return $field . '=' . $operator_rule['operator'] . strval($value);
                }
                break;
            case static::OPERATOR_EX:
                $value = $value === 'false' ? 'false' : (boolval($value) ? 'true' : 'false');
                return $field . '=' . $operator_rule['operator'] . strval($value);
                break;
            case static::OPERATOR_BETWEEN:
                if (count($value) < 2) throw new Exception('字段类型与操作符不匹配');

                list($start, $end) = $value;
                if ($data_type == DataField::FIELD_TYPE_TIME) {
                    $start = is_numeric($start) ? $start : strtotime($start);
                    $end = is_numeric($end) ? $end : strtotime($end);
                }
                return "$field={$operator_rule['operator']}$start,$end";
                break;
            case static::OPERATOR_TIME_RANGE:
                if (count($value) < 2) throw new Exception('字段类型与操作符不匹配');

                list($start, $end) = $value;
                $start = is_numeric($start) ? $start : strtotime($start);
                $end = is_numeric($end) ? $end : strtotime($end);
                if (!$start || !$end) throw new Exception('时间格式错误');
                return "time.$field=$start~$end";
                break;
            case static::OPERATOR_QUERY_STRING:
                $field = is_array($field) ? implode(',', $field) : $field;
                return "{$operator_rule['operator']}=$value" . "[$field]";
            case static::OPERATOR_MULTI_MATCH:
                return "{$operator_rule['operator']}=$value" . "[$field]~1";
            case static::OPERATOR_MATCH_PHRASE_PREFIX:
                return "{$operator_rule['operator']}=$value" . "[$field]";
            default:
                throw new Exception('操作符错误');
        }
    }

    /**
     * 转换为数据中台可以接受的filter语句
     * @return string
     * @throws Exception
     */
    public function toFilterText(): string
    {
        $text = $this->filterTextFromWhere($this->where);
        if (substr($text, 0, 1) == '(' && substr($text, -1) == ')') {
            // 需要去掉两边括号
            $text = substr($this->filterTextFromWhere($this->where), 1, -1);
        }
        return $text;
    }

    /**
     * 转换为数据中台可以接受的filter对象
     * @return array
     * @throws Exception
     */
    public function toFilterObj(): array
    {
        return $this->filterObjFromWhere($this->where);
    }

    public static function formatOptSearch($prex, $value)
    {
        $conditions = [];
        if (!is_array($value)) {
            $value = [$value];
        }
        foreach ($value as $optString) {
            list($status, $searchKey) = explode(',', $optString);
            $key = $prex . $status; //implode(['executeUser_' . $status, 'chs_phoneNumber_' . $status]);
            $conditions[] = [
                SearchFilterBuilder::OPERATOR_QUERY_STRING, $key,
                StringUtil::isEmpty($searchKey) ? null : '*' . $searchKey . '*'
            ];
        }
        return $conditions;
    }
}
